/**
* Copyright @ 2023 iAuto (Shanghai) Co., Ltd.
* All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are NOT permitted except as agreed by
* iAuto (Shanghai) Co., Ltd.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*/
//
import ArgumentTypes from '@scene/scene-vm/src/extension-support/argument-type';
import xmlEscape from '@scene/scene-vm/src/util/xml-escape';

const ArgumentTypesMap = (() => {
    const map = {};
    map[ArgumentTypes.NUMBER] = {
        shadow: {
            type: 'math_number',
            fieldName: 'NUM'
        }
    };
    map[ArgumentTypes.COLOR] = {
        shadow: {
            type: 'colour_picker',
            fieldName: 'COLOUR'
        }
    };
    map[ArgumentTypes.BOOLEAN] = {
        check: 'Boolean'
    };
    map[ArgumentTypes.STRING] = {
        shadow: {
            type: 'text',
            fieldName: 'TEXT'
        }
    };
    map[ArgumentTypes.MATRIX] = {
        shadow: {
            type: 'matrix',
            fieldName: 'MATRIX'
        }
    };
    map[ArgumentTypes.ANGLE] = {
        shadow: {
            type: 'math_angle',
            fieldName: 'NUM'
        }
    };
    map[ArgumentTypes.IMAGE] = {
        // Inline images are weird because they're not actually "arguments".
        // They are more analagous to the label on a block.
        fieldType: 'field_image'
    };
    map[ArgumentTypes.NOTE] = {
        shadow: {
            type: 'note',
            fieldName: 'NOTE'
        }
    };
    
    return map;
})();

export default function (runtime) {
    runtime.commonPublic = function () {
        // 
    }

    /**
     * Helper for _convertForScratchBlocks which handles linearization of argument placeholders. Called as a callback
     * from string#replace. In addition to the return value the JSON and XML items in the context will be filled.
     * @param {object} context - information shared with _convertForScratchBlocks about the block, etc.
     * @param {string} match - the overall string matched by the placeholder regex, including brackets: '[FOO]'.
     * @param {string} placeholder - the name of the placeholder being matched: 'FOO'.
     * @return {string} scratch-blocks placeholder for the argument: '%1'.
     * @private
     */
     runtime._convertPlaceholders = function (context, match, placeholderParams) {
        // Sanitize the placeholder to ensure valid XML
        const placeholder = placeholderParams.replace(/[<"&]/, '_');

        // Determine whether the argument type is one of the known standard field types
        const argInfo = context.blockInfo.arguments[placeholder] || {};
        let argTypeInfo = ArgumentTypesMap[argInfo.type] || {};

        // Field type not a standard field type, see if extension has registered custom field type
        if (!ArgumentTypesMap[argInfo.type] && context.categoryInfo.customFieldTypes[argInfo.type]) {
            argTypeInfo = context.categoryInfo.customFieldTypes[argInfo.type].ArgumentTypesInfo;
        }

        // Start to construct the scratch-blocks style JSON defining how the block should be
        // laid out
        let argJSON;

        // Most field types are inputs (slots on the block that can have other blocks plugged into them)
        // check if this is not one of those cases. E.g. an inline image on a block.
        if (argTypeInfo.fieldType === 'field_image') {
            argJSON = this._constructInlineImageJson(argInfo);
        } else {
            // Construct input value

            // Layout a block argument (e.g. an input slot on the block)
            argJSON = {
                type: 'input_value',
                name: placeholder,
            };

            const { max, min } = context.blockInfo.arguments[placeholder];

            if (min !== undefined) {
                argJSON.min = min;
            }

            if (max !== undefined) {
                argJSON.max = max;
            }

            const defaultValue =
                typeof argInfo.defaultValue === 'undefined' ? '' :
                    xmlEscape(argInfo.defaultValue.toString());

            if (argTypeInfo.check) {
                // Right now the only type of 'check' we have specifies that the
                // input slot on the block accepts Boolean reporters, so it should be
                // shaped like a hexagon
                argJSON.check = argTypeInfo.check;
            }

            let valueName;
            let shadowType;
            let fieldName;
            if (argInfo.menu) {
                const menuInfo = context.categoryInfo.menuInfo[argInfo.menu];
                if (menuInfo.acceptReporters) {
                    valueName = placeholder;
                    shadowType = this._makeExtensionMenuId(argInfo.menu, context.categoryInfo.id);
                    fieldName = argInfo.menu;
                } else {
                    argJSON.type = 'field_dropdown';
                    argJSON.options = this._convertMenuItems(menuInfo.items);
                    valueName = null;
                    shadowType = null;
                    fieldName = placeholder;
                }
            } else {
                valueName = placeholder;
                shadowType = (argTypeInfo.shadow && argTypeInfo.shadow.type) || null;
                fieldName = (argTypeInfo.shadow && argTypeInfo.shadow.fieldName) || null;
            }

            // <value> is the ScratchBlocks name for a block input.
            if (valueName) {
                context.inputList.push(`<value name="${placeholder}">`);
            }

            // The <shadow> is a placeholder for a reporter and is visible when there's no reporter in this input.
            // Boolean inputs don't need to specify a shadow in the XML.
            if (shadowType) {
                let shadowTypes = [shadowType]
                if (!argInfo.menu) {
                    shadowTypes = [context.categoryInfo.id,shadowType, valueName ]
                }
                context.inputList.push(`<shadow type="${shadowTypes.join('_')}">`);
            }

            // A <field> displays a dynamic value: a user-editable text field, a drop-down menu, etc.
            // Leave out the field if defaultValue or fieldName are not specified
            if (defaultValue && fieldName) {
                context.inputList.push(`<field name="${fieldName}">${defaultValue}</field>`);
            }

            if (shadowType) {
                context.inputList.push('</shadow>');
            }

            if (valueName) {
                context.inputList.push('</value>');
            }
        }

        const argsName = `args${context.outLineNum}`;
        const blockArgs = (context.blockJSON[argsName] = context.blockJSON[argsName] || []);
        if (argJSON) blockArgs.push(argJSON);
        const argNum = blockArgs.length;
        context.argsMap[placeholder] = argNum;

        return `%${argNum}`;
    }
}